﻿using System;
using System.Collections;
using Alphaleonis.Win32.Filesystem;
using Dokan;
using IOException = System.IO.IOException;

namespace CueMounter.DokanStructure
{
	partial class DokanDirectoryOperations : DokanOperations
	{
		public DokanDirectory[] RootDirectories;

		private string[] GetPath(string filename)
		{
			return filename
				.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar)
				.Split(new char[] { System.IO.Path.DirectorySeparatorChar, System.IO.Path.AltDirectorySeparatorChar }, StringSplitOptions.RemoveEmptyEntries);
		}

		public DokanDirectoryOperations(params DokanDirectory[] rootDirectories)
		{
			RootDirectories = rootDirectories;
		}

		public int Cleanup(string filename, DokanFileInfo info)
		{
			return DokanNet.DOKAN_SUCCESS;
		}

		public int CloseFile(string filename, DokanFileInfo info)
		{
			return DokanNet.DOKAN_SUCCESS;
		}

		public int CreateDirectory(string filename, DokanFileInfo info)
		{
			string[] path = GetPath(Path.GetDirectoryName(filename));
			foreach(DokanDirectory directory in RootDirectories)
			{
				try
				{
					DokanDirectory target = directory.GetDirectory(path);
					target.CreateDirectory(Path.GetFileName(filename));
					return DokanNet.DOKAN_SUCCESS;
				}
				catch
				{
				}
			}
			return -DokanNet.ERROR_PATH_NOT_FOUND;
		}

		public int DeleteDirectory(string filename, DokanFileInfo info)
		{
			string[] directoryPath = GetPath(Path.GetDirectoryName(filename));
			string fileName = Path.GetFileName(filename);

			foreach(var root in RootDirectories)
			{
				DokanDirectory target = root.GetDirectory(directoryPath);
				if(target != null)
				{
					try
					{
						target.DeleteDirectory(fileName);
						return DokanNet.DOKAN_SUCCESS;
					}
					catch
					{
						return -DokanNet.ERROR_ACCESS_DENIED;
					}
				}
			}
			return -DokanNet.ERROR_FILE_NOT_FOUND;
		}

		public int DeleteFile(string filename, DokanFileInfo info)
		{
			string[] directoryPath = GetPath(Path.GetDirectoryName(filename));
			string fileName = Path.GetFileName(filename);

			foreach(var root in RootDirectories)
			{
				DokanDirectory target = root.GetDirectory(directoryPath);
				if(target != null)
				{
					try
					{
						target.DeleteFile(fileName);
						return DokanNet.DOKAN_SUCCESS;
					}
					catch
					{
						return -DokanNet.ERROR_ACCESS_DENIED;
					}
				}
			}
			return -DokanNet.ERROR_FILE_NOT_FOUND;
		}

		public int FindFiles(string filename, ArrayList files, DokanFileInfo info)
		{
			files.Add(CreateDirectoryFileInformation("."));
			files.Add(CreateDirectoryFileInformation(".."));

			string[] path = GetPath(filename);

			foreach(var root in RootDirectories)
			{
				DokanDirectory target = root.GetDirectory(path);
				if(target != null)
				{
					if(target.Directories != null)
					{
						foreach(var dir in target.Directories)
						{
							files.Add(CreateDirectoryFileInformation(dir));
						}
					}
					if(target.Files != null)
					{
						foreach(var file in target.Files)
						{
							files.Add(CreateFileInformation(file));
						}
					}
				}
			}

			return DokanNet.DOKAN_SUCCESS;
		}

		public int FlushFileBuffers(string filename, DokanFileInfo info)
		{
			return DokanNet.DOKAN_SUCCESS;
		}

		public int GetDiskFreeSpace(ref ulong freeBytesAvailable, ref ulong totalBytes, ref ulong totalFreeBytes, DokanFileInfo info)
		{
			freeBytesAvailable = 5L * 1024 * 1024 * 1024 * 1024;
			totalFreeBytes = 5L * 1024 * 1024 * 1024 * 1024;
			totalBytes = 10L * 1024 * 1024 * 1024 * 1024;
			return DokanNet.DOKAN_SUCCESS;
		}

		public int GetFileInformation(string filename, FileInformation fileinfo, DokanFileInfo info)
		{
			string[] path = GetPath(filename);
			foreach(var root in RootDirectories)
			{
				DokanDirectory dir = root.GetDirectory(path);
				if(dir != null)
				{
					FillDirectoryFileInformation(dir, fileinfo);
					return DokanNet.DOKAN_SUCCESS;
				}
				DokanFile file = root.GetFile(path);
				if(file != null)
				{
					FillFileInformation(file, fileinfo);
					return DokanNet.DOKAN_SUCCESS;
				}
			}
			return -DokanNet.ERROR_FILE_NOT_FOUND;
		}

		public int LockFile(string filename, long offset, long length, DokanFileInfo info)
		{
			return DokanNet.DOKAN_SUCCESS;
		}

		public int MoveFile(string filename, string newname, bool replace, DokanFileInfo info)
		{
			string[] path = GetPath(filename);
			string[] newPath = GetPath(newname);

			foreach(var root in RootDirectories)
			{
				if(root.GetFile(path) != null)
				{
					try
					{
						root.MoveFile(path, newPath, replace);
						return DokanNet.DOKAN_SUCCESS;
					}
					catch
					{
						return -DokanNet.ERROR_ACCESS_DENIED;
					}
				}

				if(root.GetDirectory(path) != null)
				{
					try
					{
						root.MoveDirectory(path, newPath, replace);
						info.IsDirectory = true;
						return DokanNet.DOKAN_SUCCESS;
					}
					catch
					{
						return -DokanNet.ERROR_ACCESS_DENIED;
					}
				}
			}
			return -DokanNet.ERROR_FILE_NOT_FOUND;
		}

		public int OpenDirectory(string filename, DokanFileInfo info)
		{
			string[] path = GetPath(filename);
			foreach(var root in RootDirectories)
			{
				if(root.GetDirectory(path) != null)
				{
					return DokanNet.DOKAN_SUCCESS;
				}
			}
			return -DokanNet.ERROR_FILE_NOT_FOUND;
		}

		public int ReadFile(string filename, byte[] buffer, ref uint readBytes, long offset, DokanFileInfo info)
		{
			string[] path = GetPath(filename);
			foreach(var root in RootDirectories)
			{
				DokanFile file = root.GetFile(path);
				if(file != null)
				{
					try
					{
						readBytes = (uint)file.Read(offset, buffer, 0, buffer.Length);
						return DokanNet.DOKAN_SUCCESS;
					}
					catch
					{
						return DokanNet.DOKAN_ERROR;
					}
				}
			}
			return -DokanNet.ERROR_FILE_NOT_FOUND;
		}

		public int SetAllocationSize(string filename, long length, DokanFileInfo info)
		{
			return DokanNet.DOKAN_SUCCESS;
		}

		public int SetEndOfFile(string filename, long length, DokanFileInfo info)
		{
			string[] path = GetPath(filename);
			foreach(var root in RootDirectories)
			{
				DokanFile file = root.GetFile(path);
				if(file != null)
				{
					if(!file.CanWrite)
					{
						return -DokanNet.ERROR_ACCESS_DENIED;
					}
					try
					{
						file.Length = length;
						return DokanNet.DOKAN_SUCCESS;
					}
					catch
					{
						return DokanNet.DOKAN_ERROR;
					}
				}
			}
			return -DokanNet.ERROR_FILE_NOT_FOUND;
		}

		public int SetFileAttributes(string filename, System.IO.FileAttributes attr, DokanFileInfo info)
		{
			return DokanNet.DOKAN_SUCCESS;
		}

		public int SetFileTime(string filename, DateTime ctime, DateTime atime, DateTime mtime, DokanFileInfo info)
		{
			string[] path = GetPath(filename);
			foreach(var root in RootDirectories)
			{
				DokanDirectory dir = root.GetDirectory(path);
				if(dir != null)
				{
					try
					{
						dir.Dates = new DokanFileDates(ctime, atime, mtime);
						info.IsDirectory = true;
						return DokanNet.DOKAN_SUCCESS;
					}
					catch
					{
						return -DokanNet.ERROR_ACCESS_DENIED;
					}
				}

				DokanFile file = root.GetFile(path);
				if(file != null)
				{
					try
					{
						file.Dates = new DokanFileDates(ctime, atime, mtime);
						return DokanNet.DOKAN_SUCCESS;
					}
					catch
					{
						return -DokanNet.ERROR_ACCESS_DENIED;
					}
				}
			}
			return -DokanNet.ERROR_FILE_NOT_FOUND;
		}

		public int UnlockFile(string filename, long offset, long length, DokanFileInfo info)
		{
			return DokanNet.DOKAN_SUCCESS;
		}

		public int Unmount(DokanFileInfo info)
		{
			return DokanNet.DOKAN_SUCCESS;
		}

		public int WriteFile(string filename, byte[] buffer, ref uint writtenBytes, long offset, DokanFileInfo info)
		{
			string[] path = GetPath(filename);
			foreach(var root in RootDirectories)
			{
				DokanFile file = root.GetFile(path);
				if(file != null)
				{
					if(!file.CanWrite)
					{
						return -DokanNet.ERROR_ACCESS_DENIED;
					}
					try
					{
						writtenBytes = (uint)file.Write(offset, buffer, 0, buffer.Length);
						return DokanNet.DOKAN_SUCCESS;
					}
					catch(IOException)
					{
						return -DokanNet.ERROR_ACCESS_DENIED;
					}
					catch
					{
						return DokanNet.DOKAN_ERROR;
					}
				}
			}
			return -DokanNet.ERROR_FILE_NOT_FOUND;
		}
	}
}
